package com.hzw.net.vm

import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hzw.core.log.logE
import com.hzw.net.annotation.BindRepository
import com.hzw.net.manager.RepositoryManager
import com.hzw.net.net.exception.ResourceException
import com.hzw.net.net.exception.convert
import com.hzw.net.net.resource.IResource
import com.hzw.net.vm.event.CommonEvent
import com.hzw.net.vm.event.Event
import com.hzw.net.vm.event.ICommonEvent
import kotlinx.coroutines.*

/**
 *
 * @Author:         HuaZhongWei
 * @CreateDate:     2020/4/18 9:42 PM
 *
 * @Description:    APP的顶层ViewModel
 *
 */
open class AbsViewModel : ViewModel(), ICommonEvent {

    init {
        initRepository()
    }

    private val _eventLiveData by lazy {
        MutableLiveData<CommonEvent>()
    }

    private fun initRepository() {
        javaClass.declaredFields.filter { filter ->
            filter.isAnnotationPresent(BindRepository::class.java)
        }.forEach { field ->
            val repository: AbsRepository =
                RepositoryManager.getRepository(field.getAnnotation(BindRepository::class.java)?.repository)
            field.set(this, repository)
        }
    }

    val eventLiveData: LiveData<CommonEvent> = _eventLiveData

    fun launchAtViewModel(block: suspend CoroutineScope.() -> Unit) {
        viewModelScope.launch {
            block()
        }
    }

    /**
     * 处理特殊事件
     */
    @MainThread
    fun handleSpecialException(exception: ResourceException) {
        "code = ${exception.code}, message = ${exception.message}".logE()
        handleSpecialEvent(exception.code)
    }

    /**
     * 首先打印日志
     * 然后转换为ResourceException
     * 异常向界面传递
     */
    @MainThread
    fun handleThrowable(throwable: Throwable?): ResourceException {
        throwable?.printStackTrace()
        val exception = throwable.convert()
        handleSpecialException(exception)
        return exception
    }


    /**
     * 检测请求是否成功
     */
    fun <T> checkSuccess(resource: IResource<T>): T {
        if (!resource.isSuccess()) {
            throw ResourceException(resource)
        }
        return resource.data()
    }

    fun handleException(
        block: suspend CoroutineScope.() -> Unit,
        error: ((ResourceException) -> Unit)? = null,
        complete: suspend CoroutineScope.() -> Unit
    ) {
         launchAtViewModel {
            try {
                block()
            } catch (e: Exception) {
                withContext(Dispatchers.Main) {
                    val exception = handleThrowable(e)
                    error?.invoke(exception)
                }
            } finally {
                complete()
            }
        }
    }

    /**
     * 网络请求，success只负责请求成功的处理
     * 该方法只适用于简单的单个请求，复合型请求，还是建议用flow
     */
    fun <T> launchSuccess(
        block: suspend () -> IResource<T>,
        success: ((T) -> Unit)? = null,
        error: ((ResourceException) -> Unit)? = null,
        complete: (() -> Unit)? = null,
        isShowLoading: Boolean = true,
        isShowLoadingHideOther: Boolean = true
    ) {
        if (isShowLoading) {
            showLoading(isShowLoadingHideOther)
        }
        launchAtViewModel {
            try {
                val resource = withContext(Dispatchers.IO) {
                    block()
                }
                val data = checkSuccess(resource)
                success?.invoke(data)
                if (isShowLoadingHideOther) {
                    showContent()
                }
            } catch (e: Exception) {
                val exception = handleThrowable(e)
                error?.invoke(exception)
            } finally {
                hideLoading()
                complete?.invoke()
            }
        }
    }

    override fun showLoading(isHideOther: Boolean) {
        _eventLiveData.value = CommonEvent(
            event = Event.SHOW_LOADING,
            showLoadingHideOther = isHideOther
        )
    }

    @MainThread
    override fun hideLoading() {
        _eventLiveData.postValue(CommonEvent(Event.HIDE_LOADING))
    }

    @MainThread
    override fun showContent() {
        _eventLiveData.value = CommonEvent(Event.CONTENT)
    }

    override fun showError() {
        _eventLiveData.value = CommonEvent(Event.ERROR)
    }

    @MainThread
    override fun showError(imageResId: Int, tipText: String?, retryText: String?) {
        _eventLiveData.value = CommonEvent(Event.ERROR)
    }

    override fun showEmpty() {
        _eventLiveData.value = CommonEvent(Event.EMPTY)
    }

    @MainThread
    override fun showEmpty(imageResId: Int, tipText: String?, retryText: String?) {
        _eventLiveData.value = CommonEvent(Event.EMPTY)
    }

    @MainThread
    override fun showToast(msg: String?) {
        _eventLiveData.value = CommonEvent(Event.TOAST, msg = msg)
    }

    @MainThread
    override fun handleSpecialEvent(code: Int) {
        _eventLiveData.value = CommonEvent(Event.SPECIAL, code)
    }
}